
typedef bool ValidateCustomRuleFunction(ValidateRule rule, Map<String,dynamic> formData);
typedef Future<bool> ValidateCustomRuleFutureFunction(ValidateRule rule, Map<String,dynamic> formData);

class ValidateRule {
  final String field;
  final bool required;
  final int max;
  final int min;
  final String msg;
  final RegExp regex;
  final ValidateCustomRuleFunction custom;
  final ValidateCustomRuleFutureFunction customAsync;
  ValidateRule({
    this.field = "field", 
    this.required = false,
    this.max,
    this.min, 
    this.msg = 'invalid field value.',
    this.regex, 
    this.custom, 
    this.customAsync
  });
}

class ValidateResult extends ValidateRule {
  final bool validate;
  ValidateResult({ 
    this.validate = true,
    String field, 
    bool required, 
    int max, 
    int min, 
    String msg, 
    RegExp regex, 
    ValidateCustomRuleFunction custom, 
    ValidateCustomRuleFutureFunction customAsync 
  }) :super( 
    field: field, 
    required: required,
    max: max,
    min: min,
    msg: msg,
    regex: regex,
    custom: custom,
    customAsync: customAsync,
  );

  ValidateResult.fromRule({
    this.validate = true,
    ValidateRule rule,
  }): super(
    field: rule?.field, 
    required: rule?.required,
    max: rule?.max,
    min: rule?.min,
    msg: rule?.msg,
    regex: rule?.regex,
    custom: rule?.custom,
    customAsync: rule?.customAsync,
  );
}

class Validator {
  final List<ValidateRule> rules;
  Validator(this.rules);

  Future<ValidateResult> validate(Map<String,dynamic> formData, [List<ValidateRule> _rules]) async {
     return await validateAll(formData, _rules);
  }

  Future<ValidateResult> validateOne(String field, Map<String,dynamic> formData) async {
    List<ValidateRule> _rules = rules.where((ValidateRule rule)=> rule.field == field).toList();
    return await validateAll(formData, _rules);
  }

  Future<ValidateResult> validateAll(Map<String,dynamic> formData, List<ValidateRule> _rules) async {
    if(_rules==null) _rules = this.rules;
    bool validate = true;
    for (ValidateRule rule in _rules) {
      dynamic item = formData[rule.field];
      if (rule.required) {
        validate = item is String ? item.isNotEmpty : item != null;
        if(!validate) return ValidateResult.fromRule(validate: validate, rule: rule);
      }
      if(rule.max != null && (item is String || item is List)) {
        validate = item.length <= rule.max;
        if(!validate) return ValidateResult.fromRule(validate: validate, rule: rule);
      }
      if(rule.min != null && (item is String || item is List)) {
        validate = item.length >= rule.min;
        if(!validate) return ValidateResult.fromRule(validate: validate, rule: rule);
      }
      if(rule.regex != null && item is String) {
         validate = rule.regex.hasMatch(item);
        if(!validate) return ValidateResult.fromRule(validate: validate, rule: rule);
      }
      if(rule.custom != null) {
        validate = rule.custom(rule, formData);
        if(!validate) return ValidateResult.fromRule(validate: validate, rule: rule);
      }
      if(rule.customAsync != null) {
        validate = await rule.customAsync(rule, formData);
        if(!validate) return ValidateResult.fromRule(validate: validate, rule: rule);
      }
    }
    return ValidateResult(validate: validate);
  }
}